/**
 *
 */
package com.browseengine.bobo.facets.filter;

import java.io.IOException;

import org.apache.lucene.search.DocIdSetIterator;

import com.browseengine.bobo.api.BoboSegmentReader;
import com.browseengine.bobo.docidset.RandomAccessDocIdSet;
import com.browseengine.bobo.facets.FacetHandler;
import com.browseengine.bobo.facets.data.FacetDataCache;
import com.browseengine.bobo.facets.impl.GeoSimpleFacetHandler;
import com.browseengine.bobo.util.BigSegmentedArray;

public final class GeoSimpleFacetFilter extends RandomAccessFilter {

  private final FacetHandler<FacetDataCache<?>> _latFacetHandler;
  private final FacetHandler<FacetDataCache<?>> _longFacetHandler;
  private final String _latRangeString;
  private final String _longRangeString;

  /**
   * @param latHandler
   * @param longHandler
   * @param latRangeString
   * @param longRangeString
   */
  public GeoSimpleFacetFilter(FacetHandler<FacetDataCache<?>> latHandler,
      FacetHandler<FacetDataCache<?>> longHandler, String latRangeString, String longRangeString) {
    _latFacetHandler = latHandler;
    _longFacetHandler = longHandler;
    _latRangeString = latRangeString;
    _longRangeString = longRangeString;
  }

  private final static class GeoSimpleDocIdSetIterator extends DocIdSetIterator {
    private int _doc = -1;
    private int _minID = Integer.MAX_VALUE;
    private int _maxID = -1;
    private final int _latStart;
    private final int _latEnd;
    private final int _longStart;
    private final int _longEnd;
    private final BigSegmentedArray _latOrderArray;

    GeoSimpleDocIdSetIterator(int latStart, int latEnd, int longStart, int longEnd,
        FacetDataCache<?> latDataCache, FacetDataCache<?> longDataCache) {
      _latStart = latStart;
      _longStart = longStart;
      _latEnd = latEnd;
      _longEnd = longEnd;
      for (int i = latStart; i <= latEnd; ++i) {
        _minID = Math.min(_minID, latDataCache.minIDs[i]);
        _maxID = Math.max(_maxID, latDataCache.maxIDs[i]);
      }
      for (int i = longStart; i <= longEnd; ++i) {
        _minID = Math.min(_minID, longDataCache.minIDs[i]);
        _maxID = Math.max(_maxID, longDataCache.maxIDs[i]);
      }
      _doc = Math.max(-1, _minID - 1);
      _latOrderArray = latDataCache.orderArray;
    }

    @Override
    final public int docID() {
      return _doc;
    }

    @Override
    final public int nextDoc() throws IOException {
      int latIndex;
      int longIndex;
      while (++_doc < _maxID) { // not yet reached end
        latIndex = _latOrderArray.get(_doc);
        longIndex = _latOrderArray.get(_doc);
        if ((latIndex >= _latStart && latIndex <= _latEnd)
            && (longIndex >= _longStart && longIndex <= _longEnd)) return _doc;
      }
      return DocIdSetIterator.NO_MORE_DOCS;
    }

    @Override
    final public int advance(int id) throws IOException {
      if (_doc < id) {
        _doc = id - 1;
      }
      int latIndex;
      int longIndex;
      while (++_doc < _maxID) { // not yet reached end
        latIndex = _latOrderArray.get(_doc);
        longIndex = _latOrderArray.get(_doc);
        if ((latIndex >= _latStart && latIndex <= _latEnd)
            && (longIndex >= _longStart && longIndex <= _longEnd)) {
          return _doc;
        }
      }
      return DocIdSetIterator.NO_MORE_DOCS;
    }

    @Override
    public long cost() {
      // TODO Auto-generated method stub
      return 0;
    }
  }

  @Override
  public RandomAccessDocIdSet getRandomAccessDocIdSet(BoboSegmentReader reader) throws IOException {
    final FacetDataCache<?> latDataCache = _latFacetHandler.getFacetData(reader);
    final FacetDataCache<?> longDataCache = _longFacetHandler.getFacetData(reader);

    final int[] latRange = FacetRangeFilter.parse(latDataCache, _latRangeString);
    final int[] longRange = FacetRangeFilter.parse(longDataCache, _longRangeString);
    if ((latRange == null) || (longRange == null)) return null;

    return new RandomAccessDocIdSet() {
      int _latStart = latRange[0];
      int _latEnd = latRange[1];
      int _longStart = longRange[0];
      int _longEnd = longRange[1];

      @Override
      final public boolean get(int docid) {
        int latIndex = latDataCache.orderArray.get(docid);
        int longIndex = longDataCache.orderArray.get(docid);
        return latIndex >= _latStart && latIndex <= _latEnd && longIndex >= _longStart
            && longIndex <= _longEnd;
      }

      @Override
      public DocIdSetIterator iterator() {
        return new GeoSimpleDocIdSetIterator(_latStart, _latEnd, _longStart, _longEnd,
            latDataCache, longDataCache);
      }
    };
  }

  public static int[] parse(FacetDataCache<?> latDataCache, FacetDataCache<?> longDataCache,
      String rangeString) {
    GeoSimpleFacetHandler.GeoLatLonRange range = GeoSimpleFacetHandler.GeoLatLonRange
        .parse(rangeString);
    // ranges[0] is latRangeStart, ranges[1] is latRangeEnd, ranges[2] is longRangeStart, ranges[3]
    // is longRangeEnd
    String latLower = String.valueOf(range.latStart);
    String latUpper = String.valueOf(range.latEnd);
    String longLower = String.valueOf(range.lonStart);
    String longUpper = String.valueOf(range.lonEnd);

    int latStart, latEnd, longStart, longEnd;
    if (latLower == null) latStart = 1;
    else {
      latStart = latDataCache.valArray.indexOf(latLower);
      if (latStart < 0) {
        latStart = -(latStart + 1);
      }
    }

    if (longLower == null) longStart = 1;
    else {
      longStart = longDataCache.valArray.indexOf(longLower);
      if (longStart < 0) {
        longStart = -(longStart + 1);
      }
    }

    if (latUpper == null) {
      latEnd = latDataCache.valArray.size() - 1;
    } else {
      latEnd = latDataCache.valArray.indexOf(latUpper);
      if (latEnd < 0) {
        latEnd = -(latEnd + 1);
        latEnd = Math.max(0, latEnd - 1);
      }
    }

    if (longUpper == null) {
      longEnd = longDataCache.valArray.size() - 1;
    } else {
      longEnd = longDataCache.valArray.indexOf(longUpper);
      if (longEnd < 0) {
        longEnd = -(longEnd + 1);
        longEnd = Math.max(0, longEnd - 1);
      }
    }

    return new int[] { latStart, latEnd, longStart, longEnd };
  }

}
